package avicit.bdp.dms.oauth2.token;

import avicit.bdp.common.utils.redis.RedisCacheHelper;
import avicit.bdp.core.util.json.JSONUtils;
import avicit.bdp.dms.tdm.dto.AppClientDTO;
import avicit.bdp.dms.tdm.service.AppClientService;
import avicit.bdp.dms.tdm.utils.TdmConstants;
import avicit.platform6.commons.utils.JsonHelper;
import avicit.platform6.core.shiroSecurity.passwordencoder.Base64;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.apache.commons.lang3.StringUtils;
import org.apache.oltu.oauth2.as.response.OAuthASResponse;
import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
import org.apache.oltu.oauth2.common.message.OAuthResponse;
import org.apache.oltu.oauth2.common.utils.OAuthUtils;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Set;

/**
 * TODO
 *
 * @author xugb
 * @date 2024/4/15 10:11
 */

@Service
public class OauthTokenService {
    private static final Logger logger = LoggerFactory.getLogger(OauthTokenService.class);

    @Autowired
    private AppClientService appClientService;

    @Autowired
    private RedisCacheHelper redisCacheHelper;

    public OauthTokenService() {
    }

    public OAuthResponse checkToken(String token, boolean isPlatform, String host, String url, String op) throws OAuthSystemException {
        if (StringUtils.isEmpty(token)) {
            logger.error("token is empty");
            return OAuthResponse.errorResponse(401).setError("invalid_token").setErrorDescription("token失效！").buildJSONMessage();
        } else {
            String clientId;
            AccessToken userToken;
            String tokenScope;
            try {
                JWTOauthUtils.verifyToken(token);
                DecodedJWT jwt = JWTOauthUtils.decodeToken(token);
                clientId = jwt.getClaim("clientId").asString();
                String loginName = jwt.getClaim("USER_CODE").asString();
                tokenScope = jwt.getClaim("scope").asString();
                String authId = AuthenticationIdGenerator.generate(clientId, loginName, tokenScope);
                if (!this.redisCacheHelper.exists(TdmConstants.OAUTH_PT_OAUTH_TOKEN + clientId + ":" + authId)) {
                    throw OAuthUtils.handleOAuthProblemException("token失效！");
                }

                userToken = JSONUtils.parseObject(this.redisCacheHelper.get(TdmConstants.OAUTH_PT_OAUTH_TOKEN + clientId + ":" + authId), AccessToken.class);
                if (!token.equals(userToken.getAccessToken()) && !this.redisCacheHelper.exists(TdmConstants.OAUTH_PT_EXPIRE_TOKEN + jwt.getId())) {
                    throw OAuthUtils.handleOAuthProblemException("token失效！");
                }
            } catch (Exception var16) {
                logger.error(var16.getMessage(), var16);
                return OAuthResponse.errorResponse(401).setError("expired_token").setErrorDescription("token失效！").buildJSONMessage();
            }

            AppClientDTO clientDetail = getAppClientById(clientId);
            if (clientDetail != null && 1 != clientDetail.getArchived()) {

                return this.createTokenResponse(userToken, isPlatform);
            } else {
                logger.error("客户端[" + clientId + "]不存在");
                return OAuthResponse.errorResponse(401).setError("invalid_token").setErrorDescription("token失效！").buildJSONMessage();
            }
        }
    }

    protected OAuthResponse createTokenResponse(AccessToken accessToken, boolean isPlatform) throws OAuthSystemException {
        OAuthASResponse.OAuthTokenResponseBuilder builder = OAuthASResponse.tokenResponse(200).setAccessToken(accessToken.getAccessToken()).setExpiresIn(String.valueOf(accessToken.currentTokenExpiredSeconds())).setTokenType("Bearer");

        String refreshToken = accessToken.getRefreshToken();
        if (StringUtils.isNotEmpty(refreshToken)) {
            builder.setRefreshToken(refreshToken);
        }

        return builder.buildJSONMessage();
    }

    public AccessToken createPasswordToken(String clientId, Set<String> scopes, String loginName) {
        String scope = OAuthUtils.encodeScopes(scopes);
        String authId = AuthenticationIdGenerator.generate(clientId, loginName, scope);
        if (this.redisCacheHelper.exists(TdmConstants.OAUTH_PT_OAUTH_TOKEN + clientId + ":" + authId)) {
            AccessToken userToken = JSONUtils.parseObject(this.redisCacheHelper.get(TdmConstants.OAUTH_PT_OAUTH_TOKEN + clientId + ":" + authId), AccessToken.class);

            try {
                JWTOauthUtils.verifyToken(userToken.getAccessToken());
                return userToken;
            } catch (Exception var9) {
                DecodedJWT jwtRefresh = JWTOauthUtils.decodeToken(userToken.getRefreshToken());
                logger.debug("access_token invalid,create new one,client_id: {}", clientId);
                this.redisCacheHelper.del(TdmConstants.OAUTH_PT_OAUTH_TOKEN + clientId + ":" + authId);
                this.redisCacheHelper.del(TdmConstants.OAUTH_PT_OAUTH_REFRESH_TOKEN + clientId + ":" + jwtRefresh.getId());
            }
        }

        return this.getAccessToken(clientId, loginName, scope, authId, true);
    }

    public AccessToken changeAccessTokenByRefreshToken(String refreshToken, String clientId) throws OAuthSystemException {
        DecodedJWT jwtRefresh = JWTOauthUtils.decodeToken(refreshToken);
        String refreshTokenId = jwtRefresh.getId();
        AccessToken oldToken = getAccessTokenByRefreshToken(refreshTokenId, clientId);
        String accesstoken = oldToken.getAccessToken();
        DecodedJWT jwtAccess = JWTOauthUtils.decodeToken(accesstoken);

        String scope = jwtAccess.getClaim("scope") != null ? jwtAccess.getClaim("scope").asString() : null;
        String authId = AuthenticationIdGenerator.generate(clientId, clientId, scope);
        this.redisCacheHelper.del(TdmConstants.OAUTH_PT_OAUTH_TOKEN + clientId + ":" + authId);
        //this.redisCacheHelper.del(TdmConstants.OAUTH_PT_OAUTH_REFRESH_TOKEN + clientId + ":" + jwtRefresh.getId());
        AccessToken newToken = this.getAccessToken(clientId, clientId, scope, authId, false);
        this.redisCacheHelper.set(TdmConstants.OAUTH_PT_EXPIRE_TOKEN + jwtAccess.getId(), accesstoken, 60L);
        return newToken;
    }

    @NotNull
    private AccessToken getAccessToken(String clientId, String username, String scope, String authId, boolean hasRefreshToken) {

        AppClientDTO clientDetail = getAppClientById(clientId);

        AccessToken userToken = JWTOauthUtils.createAuthAccessToken(clientId, scope, clientId, "1", "zh_CN", "", "", "", "", (String) null, "", "", clientDetail.getClientName(), hasRefreshToken, String.valueOf(clientDetail.getAccessTokenValidity()), String.valueOf(clientDetail.getRefreshTokenValidity()));
        String redisUserToken = JsonHelper.getInstance().writeValueAsString(userToken);
        redisUserToken = new String(Base64.encode(redisUserToken.getBytes()));
        if (hasRefreshToken) {
            DecodedJWT newjwtRefresh = JWTOauthUtils.decodeToken(userToken.getRefreshToken());
            this.redisCacheHelper.set(TdmConstants.OAUTH_PT_OAUTH_REFRESH_TOKEN + clientId + ":" + newjwtRefresh.getId(), redisUserToken, Long.parseLong(userToken.getRefreshTime()));
        }

        this.redisCacheHelper.set(TdmConstants.OAUTH_PT_OAUTH_TOKEN + clientId + ":" + authId, redisUserToken, Long.parseLong(userToken.getExpireTime()));
        return userToken;
    }

    public AccessToken createClientCredentialsToken(String clientId, Set<String> scopes) {
        String scope = OAuthUtils.encodeScopes(scopes);
        AppClientDTO clientDetail = getAppClientById(clientId);
        String authId = AuthenticationIdGenerator.generate(clientId, clientId, scope);
        AccessToken userToken;
        if (this.redisCacheHelper.exists(TdmConstants.OAUTH_PT_OAUTH_TOKEN + clientId + ":" + authId)) {
            String redisUserToken = this.redisCacheHelper.get(TdmConstants.OAUTH_PT_OAUTH_TOKEN + clientId + ":" + authId);
            redisUserToken = new String(Base64.decode(redisUserToken.getBytes()));
            userToken = JSONUtils.parseObject(redisUserToken, AccessToken.class);

            try {
                JWTOauthUtils.verifyToken(userToken.getAccessToken());
                return userToken;
            } catch (Exception var8) {
                logger.debug("access_token invalid,create new one,client_id: {}", clientId);
                this.redisCacheHelper.del(TdmConstants.OAUTH_PT_OAUTH_TOKEN + clientId + ":" + authId);
            }
        }

        userToken = JWTOauthUtils.createAuthAccessToken(clientId, scope, clientId, "1", "zh_CN", (String) null, (String) null, (String) null, (String) null, (String) null, (String) null, clientId, (String) null, true, String.valueOf(clientDetail.getAccessTokenValidity()), String.valueOf(clientDetail.getRefreshTokenValidity()));
        DecodedJWT jwtRefresh = JWTOauthUtils.decodeToken(userToken.getRefreshToken());
        String redisUserToken = JSONUtils.toJsonString(userToken);
        redisUserToken = new String(Base64.encode(redisUserToken.getBytes()));
        this.redisCacheHelper.set(TdmConstants.OAUTH_PT_OAUTH_REFRESH_TOKEN + clientId + ":" + jwtRefresh.getId(), redisUserToken, Long.parseLong(userToken.getRefreshTime()));
        this.redisCacheHelper.set(TdmConstants.OAUTH_PT_OAUTH_TOKEN + clientId + ":" + authId, redisUserToken, Long.parseLong(userToken.getExpireTime()));

        return userToken;
    }

    public AccessToken createTrustClientCredentialsToken(String clientId, Set<String> scopes, String loginName) {
        return this.createPasswordToken(clientId, scopes, loginName);
    }

    public AccessToken getAccessTokenByRefreshToken(String refreshtokenId, String clientId) {
        if (this.redisCacheHelper.exists(TdmConstants.OAUTH_PT_OAUTH_REFRESH_TOKEN + clientId + ":" + refreshtokenId)) {
            String redisUserToken = this.redisCacheHelper.get(TdmConstants.OAUTH_PT_OAUTH_REFRESH_TOKEN + clientId + ":" + refreshtokenId);
            redisUserToken = new String(Base64.decode(redisUserToken.getBytes()));
            AccessToken userToken = JSONUtils.parseObject(redisUserToken, AccessToken.class);
            return userToken;
        } else {
            return null;
        }
    }

    public AppClientDTO getAppClientById(String clientId) {
        String appClientStr = this.redisCacheHelper.get(TdmConstants.ASSET_APP_CLIENT + clientId);
        AppClientDTO appClientDTO;
        if (StringUtils.isBlank(appClientStr)) {
            appClientDTO = appClientService.getAppClientByClientId(clientId);
            this.redisCacheHelper.set(TdmConstants.ASSET_APP_CLIENT + clientId, JSONUtils.toJson(appClientDTO));
        } else {
            appClientDTO = JSONUtils.parseObject(appClientStr, AppClientDTO.class);
        }
        return appClientDTO;
    }
}

